/*
 * MIT License
 *
 * Copyright (c) 2019 Cheng.Wei
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package tk.jfree.summer.excel;

import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import tk.jfree.summer.excel.annotation.Table;
import tk.jfree.summer.excel.exception.ExcelException;
import tk.jfree.summer.excel.metadata.Excel;
import tk.jfree.summer.excel.reflection.FieldInvoker;
import tk.jfree.summer.excel.reflection.Reflector;
import tk.jfree.summer.excel.util.StringUtil;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * Excel 数据读写
 * @author Cheng.Wei
 */
public class ExcelHelper<T> {

    private final Class<T> clazz;

    private ExcelHelper(Class<T> clazz) {
        this.clazz = clazz;
    }

    /**
     * 初始化
     * @param clazz
     * @return Excel读取结果
     */
    public static <T>ExcelHelper<T> builder(Class<T> clazz){
        return new ExcelHelper(clazz);
    }

    /**
     * 默认读取第一个 sheet=0  {@link tk.jfree.summer.excel.annotation.Table#sheet}
     * 默认start =1  {@link tk.jfree.summer.excel.annotation.Table#first}
     * @param input
     * @return excel解析数据
     * @throws IOException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public ExcelResult read(InputStream input) throws IOException {
        Table table = Optional.ofNullable(clazz.getAnnotation(Table.class))
                .orElseThrow(()-> new ExcelException(clazz+"缺少tk.jfree.summer.excel.annotation.Table注解"));
        return read(table.sheet(), table.first(), input);
    }

    /**
     * 设置读取相关参数
     * @param sheetName sheet 名称
     * @param start 数据所在的行
     * @param input
     * @return excel解析数据
     * @throws IOException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public ExcelResult read(String sheetName, int start, InputStream input) throws IOException {
        try(Workbook book = WorkbookFactory.create(input)){
            Sheet sheet = Optional.ofNullable(book.getSheet(sheetName)).orElse(book.getSheetAt(0));
            sheet.setForceFormulaRecalculation(true);
            List tList = IntStream.rangeClosed(start, sheet.getLastRowNum())
                    .mapToObj(i->get(clazz, sheet.getRow(i))).collect(Collectors.toList());
            return ExcelResult.builder().sheet(sheet.getSheetName()).firstRow(start).lastRow(sheet.getLastRowNum())
                    .data(tList).build();
        }
    }

    T get(Class<T> clazz, Row row){
        try {
            T t = clazz.getDeclaredConstructor().newInstance();
            Reflector.sheet(clazz).getResultMap().forEach(x -> FieldInvoker.convertToEntityAttribute(t, row.getCell(x.getIndex()), x));
            return t;
        } catch (Exception ex) {
            throw new ExcelException(ex);
        }
    }

    /**
     * 写入数据
     * @param data
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data){
        return write(data, new XSSFWorkbook(), true);
    }

    /**
     * 写入数据
     * @param data 数据集合
     * @param sheetName 工作表名称
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, String sheetName){
        return write(data, new XSSFWorkbook(), sheetName, Reflector.sheet(clazz).getFirst(), true);
    }

    /**
     * 写入数据
     * 默认自动创建 sheet
     * @param data 数据集合
     * @param wb 指定wb
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, Workbook wb){
        return write(data, wb, true);
    }

    /**
     * 写入数据
     * @param data 数据集合
     * @param start 开始行 （必须大于或等于 0）
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, int start){
        return write(data, start, true);
    }

    /**
     * 写入数据
     * 默认自动创建 sheet
     * @param data 数据集合
     * @param wb 指定 Workbook
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, Workbook wb, boolean createHeader){
        Excel excel = Reflector.sheet(clazz);
        return write(data, wb, excel.getSheet(), excel.getFirst(), createHeader);
    }

    /**
     * 写入数据
     * @param data
     * @param sheetName 工作表名称
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, String sheetName, boolean createHeader){
        return write(data, new XSSFWorkbook(), sheetName, Reflector.sheet(clazz).getFirst(), createHeader);
    }

    /**
     * 写入数据
     * 默认自动创建 sheet
     * @param data 数据集合
     * @param wb 指定wb
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, Workbook wb, String sheetName){
        return write(data, wb, sheetName, Reflector.sheet(clazz).getFirst(), true);
    }

    /**
     * 写入数据
     * @param data
     * @param start 开始行 （必须大于或等于 0）
     * @param createHeader 是否创建表头
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, int start, boolean createHeader){
        return write(data, new XSSFWorkbook(), Reflector.sheet(clazz).getSheet(), start, createHeader);
    }

    /**
     * 写入数据
     * @param data
     * @param workbook 工作表名称
     * @param start 开始行 （必须大于或等于 0）
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, Workbook workbook, int start){
        return write(data, workbook, Reflector.sheet(clazz).getSheet(), start, true);
    }

    /**
     * 写入数据
     * @param data
     * @param sheetName 工作表名称
     * @param start 开始行 （必须大于或等于 0）
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, String sheetName, int start){
        return write(data, new XSSFWorkbook(), sheetName, start, true);
    }

    /**
     * 写入数据
     * 默认自动创建 sheet
     * @param data 数据集合
     * @param wb 指定wb
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, Workbook wb, String sheetName, boolean createHeader){
        return write(data, wb, sheetName, Reflector.sheet(clazz).getFirst(), createHeader);
    }

    /**
     * 写入数据
     * @param data
     * @param wb 指定wb
     * @param sheetName 工作表名称
     * @param start 开始行 （必须大于或等于 0）
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, Workbook wb, String sheetName, int start){
        return write(data, wb, sheetName, start, true);
    }

    /**
     * 写入数据
     * @param data
     * @param workbook 工作表名称
     * @param start 开始行 （必须大于或等于 0）
     * @param createHeader 是否需要创建表头 true-是、 false-否
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, Workbook workbook, int start, boolean createHeader){
        return write(data, workbook, Reflector.sheet(clazz).getSheet(), start, createHeader);
    }

    /**
     * 写入数据
     * @param data
     * @param sheetName 工作表名称
     * @param start 开始行 （必须大于或等于 0）
     * @param createHeader 是否需要创建表头 true-是、 false-否
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, String sheetName, int start, boolean createHeader){
        return write(data, new XSSFWorkbook(), sheetName, start, createHeader);
    }

    /**
     * 写入数据
     * @param data
     * @param wb 指定wb
     * @param sheetName 工作表名称
     * @param start 开始行 （必须大于或等于 0）
     * @param createHeader 是否需要创建表头 true-是、 false-否
     * @return {@link Workbook}
     */
    public Workbook write(List<T> data, Workbook wb, String sheetName, int start, boolean createHeader){
        final Excel excel = Reflector.sheet(clazz);
        Sheet sheet = Optional.ofNullable(StringUtil.trim(sheetName)).isPresent()
                ? Optional.ofNullable(wb.getSheet(sheetName)).orElseGet(()->wb.createSheet(sheetName)) : wb.getSheetAt(0);
        if(createHeader){
            Row row = sheet.createRow(start-1);
            excel.getResultMap()
                    .forEach(field ->{
                        sheet.setColumnWidth(ColumnConversion.getIndex(field.getColumn()), field.getWidth());
                        row.createCell(ColumnConversion.getIndex(field.getColumn())).setCellValue(field.getNotes());});
        }
        for (T t: data) {
            Row row = sheet.createRow(start);
            excel.getResultMap().forEach(x-> FieldInvoker.convertToSheetColumn(t, row.createCell(x.getIndex()), x));
            ++start;
        }
        return sheet.getWorkbook();
    }
}
